
#ifndef __HTTP_COOKIE_H__
#define __HTTP_COOKIE_H__

#include "../HTTP.h"
#include "../knutil.h"

#include <algorithm>
#include <map>
#include <vector>
#include <string>

#include <ctime>


namespace HTTP {

class CookieValue
{
private:
	std::string version_;
	std::string val_;
	std::string port_;
	std::string path_spec_;
	std::string secure_;
	std::string expires_;
	std::string discard_;
	std::map<std::string, std::string> rest_;
public:
	CookieValue()
	{
	}

	CookieValue(
	            const std::string& version,
	            const std::string& val,
	            const std::string& port,
	            const std::string& path_spec,
	            const std::string& secure,
	            const std::string& expires,
	            const std::string& discard
	           )
	: version_(version), val_(val), port_(port), path_spec_(path_spec),
	  secure_(secure), expires_(expires), discard_(discard)
	{
	}

	void set_rest(const std::map<std::string, std::string>& rest)
	{
		rest_ = rest;
	}

	std::string get_val() const
	{
		return val_;
	}

};

class CookieMap
{
public:

	typedef CookieValue ArrayType;

	typedef std::map< std::string,
	                  std::map< std::string,
	                            std::map< std::string, ArrayType > > > MapType;
private:
	typedef std::map< std::string,
	                  std::map< std::string, ArrayType> > MapType2;
	typedef std::map< std::string, ArrayType > MapType3;

	MapType cookies_;

public:

	void add(const std::string& domain, const std::string& path, const std::string& key, const ArrayType& v)
	{
		cookies_[domain][path][key] = v;
	}

	void remove(const std::string& domain, const std::string& path, const std::string& key)
	{
		MapType::iterator i = cookies_.find(domain);
		if( i==cookies_.end() )
			return;
		MapType2::iterator i2 = i->second.find(path);
		if( i2==i->second.end() )
			return;
		MapType3::iterator i3 = i2->second.find(key);
		if( i3==i2->second.end() )
			return;
		i2->second.erase(i3);
	}

	void get(HTTP::Request& req)
	{

		const std::string host = req.host();
		const std::string path = "/";
		// Bad: path is not supported now

		
		std::string::size_type begin = 0;
		const std::string::size_type end = std::string::npos;
		
		std::string subhost;

		MapType::iterator i;
		
		while( begin != end )
		{
			subhost = host.substr(begin);

			i = cookies_.find(subhost);
			if( i!=cookies_.end() )
				break;
			begin = host.find('.', begin+1);
		}

		if(begin == end)
		{
			return;
		}

		MapType2::iterator i2 = i->second.find(path);
		if( i2==i->second.end() )
			return;
		
		
		MapType3::iterator begin3 = i2->second.begin();
		MapType3::iterator end3 = i2->second.end();
		if( begin3==end3 )
		{
			return;
		}
		std::string cook = begin3->first + "=" + begin3->second.get_val();
		++begin3;
		for(; begin3!=end3 ; ++begin3)
		{
			cook += "; ";
			cook += begin3->first;
			cook += "=";
			cook += begin3->second.get_val();
		}
		req.add_header("Cookie", cook);
		
	}

private:
	static bool match_domain(const std::string & host, const std::string& domain)
	{
		if( host.length() == 0 || domain.length() == 0 )
		{
			return false;
		}
		if( host == domain )
		{
			return true;
		}
		if( domain[0] != '.' )
		{
			return false;
		}

		std::string::size_type i = host.find(domain);
		if( i == std::string::npos )
		{
			return false;
		}
		
		return i + domain.length() == host.length();
	}


};


class Cookies
{
private:
	typedef std::map<std::string, std::string> Map;

	CookieMap cookies_;

private:

	bool beginwith(const std::string& s, char c)
	{
		return !s.empty() && s[0] == c;
	}
	
	static void split_cook(const std::string& cook, Map& m, std::string& key, std::string& value)
	{
		std::string::size_type first = 0;
		std::string::size_type next = cook.find("; ");
		const std::string::size_type end = std::string::npos;
		if( next != end )
		{
			std::string s = cook.substr(first, next-first);
			split_pair(s, key, value);
			first = next+2;
			next = cook.find("; ", first);
		}
		std::string k;
		std::string v;
		while( next != end )
		{
			std::string s = cook.substr(first, next-first);
			split_pair(s, k, v);
			m[k] = v;
			first = next+2;
			next = cook.find("; ", first);
		}
		split_pair( cook.substr(first), k, v);
		m[k] = v;
	}

	static void split_pair(const std::string& s, std::string& k, std::string& v)
	{
		std::string::size_type i = s.find('=');
		if( i == std::string::npos )
		{
			return;
		}
		k = s.substr(0, i);
		if( ++i != std::string::npos )
		{
			v = s.substr(i);
		}
	}

	static std::string extract_value(Map& m, const char* k)
	{
		std::string v = m[k];
		m.erase(k);
		return v;
	}

public:

	void extract_cookies(const HTTP::Response& resp)
	{
		std::string h = resp.get_header("Set-Cookie");
		if( h == "" )
		{
			return;
		}
		
		Map m;
		std::string key;
		std::string value;

		split_cook(h, m, key, value);

		
		std::string version = extract_value(m, "version");
		std::string path = extract_value(m, "path");
		std::string domain = extract_value(m, "domain");
		std::string port = extract_value(m, "port");
		std::string path_spec = extract_value(m, "path_spec");
		std::string secure = extract_value(m, "secure");
		std::string maxage = extract_value(m, "maxage");
		std::string discard = extract_value(m, "discard");
		
		if( path == "" )
		{
			path = "/";
		}

		if( domain == "" )
		{
			domain = resp.host();
		}

		set_cookie(version,
		           key, value, path, domain, port,
			   path_spec, secure, maxage, discard);
		
	}

	void add_cookie_header(HTTP::Request& req)
	{
		cookies_.get(req);
	}

private:

	Cookies& set_cookie(
	                std::string version,
	                const std::string& key,
	                const std::string& val,
	                const std::string& path,
	                const std::string& domain,
	                const std::string& port,
	                const std::string& path_spec,
	                const std::string& secure,
	                const std::string& maxage,
	                const std::string& discard
	               )
	{
		if( !beginwith(path, '/') || beginwith(key, '$') )
		{
			return *this;
		}

		//Bad: no port check


		std::string expires;
		if( !maxage.empty() )
		{
			int maxage_int = knlib::util::str_cast<int>(maxage);
			if( maxage_int <= 0 )
			{
				cookies_.remove(domain, path, key);
				return *this;
			}
			expires = knlib::util::str_cast<std::string>(std::time(0) + maxage_int);
		}

		if( version == "" )
		{
			version = "0";
		}

		CookieMap::ArrayType array(version, val, port, path_spec, secure, expires, discard);
		
		//Bad: no rest
		
		cookies_.add(domain, path, key, array);

		return *this;
	}

};




}

#endif

